package cn.aws360.boot.system.biz.service.impl;

import cn.aws360.boot.auth.api.constant.AuthConstant;
import cn.aws360.boot.auth.api.helper.LoginHelper;
import cn.aws360.boot.base.api.constant.enums.DelFlagEnum;
import cn.aws360.boot.base.api.exception.ServiceException;
import cn.aws360.boot.data.api.constant.DataConstant;
import cn.aws360.boot.data.api.factory.PageFactory;
import cn.aws360.boot.data.api.pojo.request.PageQuery;
import cn.aws360.boot.data.api.pojo.response.PageResult;
import cn.aws360.boot.data.api.utils.PojoUtils;
import cn.aws360.boot.system.api.constant.SysConstant;
import cn.aws360.boot.system.api.constant.enums.BucketEnum;
import cn.aws360.boot.system.api.exception.SysExceptionEnum;
import cn.aws360.boot.system.api.pojo.request.*;
import cn.aws360.boot.system.api.pojo.response.SysFileModel;
import cn.aws360.boot.system.api.pojo.response.SysUserModel;
import cn.aws360.boot.system.biz.domain.SysDept;
import cn.aws360.boot.system.biz.domain.SysUser;
import cn.aws360.boot.system.biz.domain.SysUserRole;
import cn.aws360.boot.system.biz.mapper.SysUserMapper;
import cn.aws360.boot.system.biz.service.*;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author junnan
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements SysUserService {

    private final SysConfService confService;
    private final SysDeptService deptService;
    private final SysFileService fileService;
    private final SysUserRoleService userRoleService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(value = SysConstant.MODULE_USER_CACHE, allEntries = true)
    public Boolean add(SysUserWrite sysUserWrite) {
        SysUser sysUser = BeanUtil.copyProperties(sysUserWrite, SysUser.class, DataConstant.ID);
        checkNameHasExist(sysUser);
        sysUser.setPassword(getInitPassword());
        boolean flag = this.save(sysUser);
        if (flag) {
            sysUserWrite.setId(sysUser.getId());
            return assignRole(sysUserWrite);
        }
        return false;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(value = SysConstant.MODULE_USER_CACHE, allEntries = true)
    public Boolean edit(SysUserWrite sysUserWrite) {
        Integer userId = sysUserWrite.getId();
        checkSysDontUpdate(userId);
        SysUser sysUser = this.getSysUserById(userId);
        BeanUtil.copyProperties(sysUserWrite, sysUser);
        checkNameHasExist(sysUser);
        boolean flag = this.updateById(sysUser);
        if (flag) {
            return assignRole(sysUserWrite);
        }
        return false;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(value = SysConstant.MODULE_USER_CACHE, allEntries = true)
    public Boolean reset(SysUserResetWrite sysUserResetWrite) {
        Integer userId = sysUserResetWrite.getId();
        checkSysDontUpdate(userId);
        SysUser sysUser = this.getSysUserById(userId);
        Integer lock = sysUserResetWrite.getLock();
        if (ObjectUtil.isNotNull(lock)) {
            sysUser.setLokFlag(lock == 0 ? 0 : 1);
            return this.updateById(sysUser);
        }
        Integer pass = sysUserResetWrite.getPass();
        if (ObjectUtil.isNotNull(pass) && pass == 1) {
            sysUser.setPassword(getInitPassword());
            return this.updateById(sysUser);
        }
        return false;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(value = SysConstant.MODULE_USER_CACHE, allEntries = true)
    public Boolean remove(String ids) {
        List<Integer> userIdList = Convert.toList(Integer.class, ids.split(StrUtil.COMMA));
        userIdList.forEach(this::checkSysDontDelete);
        LambdaQueryWrapper<SysUserRole> queryWrapper = Wrappers.<SysUserRole>lambdaQuery().in(SysUserRole::getUserId, userIdList);
        userRoleService.getBaseMapper().delete(queryWrapper);
        return this.removeByIds(userIdList);
    }

    @Override
    public SysUserModel detail(Integer id) {
        SysUser sysUser = this.getSysUserById(id);
        SysUserModel sysUserModel = BeanUtil.copyProperties(sysUser, SysUserModel.class);
        return expandSysUserModel(sysUserModel);
    }

    @Override
    public List<SysUserModel> findList(SysUserQuery sysUserQuery) {
        LambdaQueryWrapper<SysUser> queryWrapper = createQueryWrapper(sysUserQuery);
        List<SysUser> sysUserList = this.list(queryWrapper);
        List<SysUserModel> sysUserModelList = PojoUtils.copyToList(sysUserList, SysUserModel.class);
        return expandSysUserModel(sysUserModelList);
    }

    @Override
    public PageResult<SysUserModel> findPage(SysUserQuery sysUserQuery, PageQuery pageQuery) {
        LambdaQueryWrapper<SysUser> queryWrapper = createQueryWrapper(sysUserQuery);
        PageResult<SysUser> sysUserPage = PageFactory.getPageResult(this, pageQuery, queryWrapper);
        PageResult<SysUserModel> sysUserModelPage = PojoUtils.copyToPage(sysUserPage, SysUserModel.class);
        sysUserModelPage.setRows(expandSysUserModel(sysUserModelPage.getRows()));
        return sysUserModelPage;
    }

    @Override
    public String getInitPassword() {
        String sysInitPass = confService.getValueByCode(AuthConstant.CONF_INIT_PASS);
        if (StrUtil.isBlank(sysInitPass)) {
            sysInitPass = AuthConstant.CONF_INIT_PASS_DEFAULT;
        }
        return LoginHelper.encryptPassword(sysInitPass);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(value = SysConstant.MODULE_USER_CACHE, allEntries = true)
    public String editAvatar(MultipartFile multipartFile) {
        SysUser sysUser = this.getSysUserById(LoginHelper.getUserId());
        SysFileModel sysFileModel = fileService.upload(BucketEnum.PUBLIC.getName(),
                SysConstant.OSS_DIRECTORY_AVATAR, multipartFile);
        if (ObjectUtil.isNotNull(sysFileModel)) {
            sysUser.setAvatar(sysFileModel.getFilePath());
            this.updateById(sysUser);
            return sysUser.getAvatar();
        }
        return null;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(value = SysConstant.MODULE_USER_CACHE, allEntries = true)
    public Boolean editProfile(SysUserProfileWrite sysUserProfileWrite) {
        Integer id = sysUserProfileWrite.getId();
        checkNotOwner(id);
        SysUser sysUser = this.getSysUserById(id);
        BeanUtil.copyProperties(sysUserProfileWrite, sysUser);
        checkNameHasExist(sysUser);
        return this.updateById(sysUser);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(value = SysConstant.MODULE_USER_CACHE, allEntries = true)
    public Boolean editPassword(SysUserPasswordWrite sysUserPasswordWrite) {
        Integer id = sysUserPasswordWrite.getId();
        checkNotOwner(id);
        SysUser sysUser = this.getSysUserById(id);
        checkPassError(sysUserPasswordWrite, sysUser);
        String newPassword = sysUserPasswordWrite.getNewPassword();
        sysUser.setPassword(LoginHelper.encryptPassword(newPassword));
        return this.updateById(sysUser);
    }

    private SysUser getSysUserById(Integer id) {
        SysUser sysUser = this.getById(id);
        checkNotExist(sysUser);
        return sysUser;
    }

    private LambdaQueryWrapper<SysUser> createQueryWrapper(SysUserQuery userQuery) {
        LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>();
        Integer deptId = userQuery.getDeptId();
        if (ObjectUtil.isNotNull(deptId) && !SysConstant.ROOT_ID.equals(deptId)) {
            List<Integer> allSubIdList = deptService.findAllSubIdList(deptId);
            queryWrapper.in(SysUser::getDeptId, allSubIdList);
        }
        String name = userQuery.getName();
        if (StrUtil.isNotBlank(name)) {
            queryWrapper.and(wrapper -> wrapper
                    .like(SysUser::getUsername, name)
                    .or()
                    .like(SysUser::getNickname, name));
        }
        queryWrapper.like(ObjectUtil.isNotEmpty(userQuery.getEmail()), SysUser::getEmail, userQuery.getEmail());
        queryWrapper.like(ObjectUtil.isNotEmpty(userQuery.getMobile()), SysUser::getMobile, userQuery.getMobile());
        queryWrapper.orderByDesc(SysUser::getId);
        return queryWrapper;
    }

    private Boolean assignRole(SysUserWrite sysUserWrite) {
        Integer userId = sysUserWrite.getId();
        LambdaQueryWrapper<SysUserRole> queryWrapper = Wrappers.<SysUserRole>lambdaQuery().eq(SysUserRole::getUserId, userId);
        userRoleService.getBaseMapper().delete(queryWrapper);

        String roleIds = sysUserWrite.getRoleIds();
        if (StrUtil.isNotBlank(roleIds)) {
            List<Integer> roleIdList = Convert.toList(Integer.class, roleIds.split(StrUtil.COMMA));
            List<SysUserRole> userRoleList = new HashSet<>(roleIdList).stream().map(roleId ->
                    SysUserRole.builder().userId(userId).roleId(roleId).build()).collect(Collectors.toList());
            return userRoleService.saveBatch(userRoleList);
        }
        return true;
    }

    private SysUserModel expandSysUserModel(SysUserModel sysUserModel) {
        Integer deptId = sysUserModel.getDeptId();
        if (ObjectUtil.isNotNull(deptId)) {
            SysDept sysDept = deptService.getById(deptId);
            sysUserModel.setDeptName(ObjectUtil.isNull(sysDept) ? null : sysDept.getName());
        }

        Integer userId = sysUserModel.getId();
        LambdaQueryWrapper<SysUserRole> queryWrapper = Wrappers.<SysUserRole>lambdaQuery().eq(SysUserRole::getUserId, userId);
        List<SysUserRole> sysUserRoleList = userRoleService.list(queryWrapper);
        Set<Integer> roleIdSet = sysUserRoleList.stream().map(SysUserRole::getRoleId).collect(Collectors.toSet());
        sysUserModel.setRoleIds(StrUtil.join(StrUtil.COMMA, roleIdSet));
        return sysUserModel;
    }

    private List<SysUserModel> expandSysUserModel(List<SysUserModel> sysUserModelList) {
        Map<Integer, List<SysDept>> deptGroupMap = new HashMap<>();
        Set<Integer> deptIdSet = sysUserModelList.stream().map(SysUserModel::getDeptId).collect(Collectors.toSet());
        if (CollUtil.isNotEmpty(deptIdSet)) {
            List<SysDept> sysDeptList = deptService.listByIds(deptIdSet);
            deptGroupMap = sysDeptList.stream().collect(Collectors.groupingBy(SysDept::getId));
        }

        Map<Integer, List<SysUserRole>> menuGroupMap = new HashMap<>();
        Set<Integer> userIdSet = sysUserModelList.stream().map(SysUserModel::getId).collect(Collectors.toSet());
        if (CollUtil.isNotEmpty(userIdSet)) {
            LambdaQueryWrapper<SysUserRole> queryWrapper = Wrappers.<SysUserRole>lambdaQuery().in(SysUserRole::getUserId, userIdSet);
            List<SysUserRole> sysUserRoleList = userRoleService.list(queryWrapper);
            menuGroupMap = sysUserRoleList.stream().collect(Collectors.groupingBy(SysUserRole::getUserId));
        }

        for (SysUserModel sysUserModel : sysUserModelList) {
            Integer deptId = sysUserModel.getDeptId();
            List<SysDept> deptList = deptGroupMap.get(deptId);
            if (CollUtil.isNotEmpty(deptList)) {
                sysUserModel.setDeptName(deptList.get(0).getName());
            }

            Integer userId = sysUserModel.getId();
            List<SysUserRole> userRoleList = menuGroupMap.get(userId);
            if (CollUtil.isNotEmpty(userRoleList)) {
                Set<Integer> roleIdSet = userRoleList.stream().map(SysUserRole::getRoleId).collect(Collectors.toSet());
                sysUserModel.setRoleIds(StrUtil.join(StrUtil.COMMA, roleIdSet));
            }
        }
        return sysUserModelList;
    }

    private void checkNotOwner(Integer id) {
        if (!LoginHelper.getUserId().equals(id)) {
            throw new ServiceException(SysExceptionEnum.USER_NOT_OWNER);
        }
    }

    private void checkNotExist(SysUser sysUser) {
        if (ObjectUtil.isNull(sysUser) || DelFlagEnum.Y.getValue().equals(sysUser.getDelFlag())) {
            throw new ServiceException(SysExceptionEnum.USER_NOT_EXIST);
        }
    }

    private void checkNameHasExist(SysUser sysUser) {
        Integer id = sysUser.getId();
        String username = sysUser.getUsername();
        id = (ObjectUtil.isNotNull(id) && id != 0) ? id : -1;
        SysUser existUser = this.getOne(Wrappers.<SysUser>lambdaQuery().eq(SysUser::getUsername, username));
        if (ObjectUtil.isNotNull(existUser) && !existUser.getId().equals(id)) {
            throw new ServiceException(SysExceptionEnum.USER_NAME_HAS_EXIST, username);
        }
    }

    private void checkSysDontUpdate(Integer userId) {
        if (SysConstant.ROOT_ID.equals(userId)) {
            throw new ServiceException(SysExceptionEnum.USER_DONT_UPDATE_FOR_ROOT);
        }
    }

    private void checkPassError(SysUserPasswordWrite sysUserPasswordWrite, SysUser sysUser) {
        String newPassword = sysUserPasswordWrite.getNewPassword();
        if (!newPassword.equals(sysUserPasswordWrite.getNewPasswordConfirm())) {
            throw new ServiceException(SysExceptionEnum.USER_DONT_UPDATE_FOR_PASS_NOT_SAME);
        }

        String oldPassword = sysUserPasswordWrite.getOldPassword();
        if (!LoginHelper.matchesPassword(oldPassword, sysUser.getPassword())) {
            throw new ServiceException(SysExceptionEnum.USER_DONT_UPDATE_FOR_PASS_OLD_ERROR);
        }
    }

    private void checkSysDontDelete(Integer userId) {
        if (SysConstant.ROOT_ID.equals(userId)) {
            throw new ServiceException(SysExceptionEnum.USER_DONT_DELETE_FOR_ROOT);
        }
    }

}
